package com.ruoyi.web.controller.api;

import cn.hutool.core.date.DateUtil;
import com.ruoyi.cat.domain.AdIprecord;
import com.ruoyi.cat.domain.AdMedium;
import com.ruoyi.cat.domain.AdMediumData;
import com.ruoyi.cat.domain.VO.AdNumVO;
import com.ruoyi.cat.service.IAdFinanceService;
import com.ruoyi.cat.service.IAdIprecordService;
import com.ruoyi.cat.service.IAdMediumDataService;
import com.ruoyi.cat.service.IAdMediumService;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.IpRegion;
import com.ruoyi.common.utils.ip.IpUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.*;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.io.IOException;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

@Slf4j
@Api(tags = "api-新增广告接口")
@RestController
@RequestMapping("/api/newAd")
public class ApiAdController extends BaseController {

    @Autowired
    private IAdFinanceService adFinanceService;

    @Autowired
    private IAdMediumService adMediumService;

    @Autowired
    private IAdIprecordService adIprecordService;

    @Autowired
    public RedisTemplate redisTemplate;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private IAdMediumDataService iAdMediumDataService;



    @GetMapping("/getFinance")
    @ApiOperation("获取广告内容")
    public AjaxResult getFinance() {
        return success(adFinanceService.selectAdFinance());
    }

    @GetMapping("/getAdDataByPosition")
    @ApiOperation("根据状态获取广告接口")
    public AjaxResult getAdDataByPosition(@RequestParam String position) {
        log.info("getAdDataByPosition:{}", position);
        return success(adMediumService.getAdDataByPosition(position));
    }

/*    @PostMapping("/recordAddress")
    @ApiOperation("记录点击广告的ip地址接口")
    public AjaxResult recordAddress(HttpServletRequest request, @RequestBody @Valid AdNumVO adNumVO) {
        String ipAddress = IpUtils.getIpAddr(request);
        System.out.println("ipAddress2----"+ ipAddress);
        IpRegion currentArea = IpAddressUtil.parseIpLocation(ipAddress);

    }*/

   /* @PostMapping("/recordAddress")
    @ApiOperation("记录点击广告的ip地址接口")
    public AjaxResult recordAddress(HttpServletRequest request, @RequestBody @Valid AdNumVO adNumVO) {
        // 获取IP地址
        String ipAddress = IpUtils.getIpAddr(request);
        System.out.println("ipAddress2----"+ ipAddress);
        AdMedium adMedium =  adMediumService.selectAdMediumById(adNumVO.getAdId());
        if ( adMedium.getCompleted() >= adMedium.getAdvertisingVolume()){
            adMedium.setState("3");
            adMediumService.updateAdMedium(adMedium);
        }

        // 解析IP地址获取地理位置
        IpRegion currentArea = IpAddressUtil.parseIpLocation(ipAddress);

        AdIprecord adStatistics = new AdIprecord();
        adStatistics.setArticleId(adNumVO.getAdId());
        adStatistics.setIpValue(ipAddress);
        adStatistics.setAreaValue(currentArea != null ? currentArea.getCity() : "未知");
        adStatistics.setPv(1L);
        adStatistics.setDayDate(new Date());
        adIprecordService.insertAdIprecord(adStatistics);

        // ===== Redis实现PV和UV统计 =====
        // Redis键名定义 - 仅记录当天数据
        String pvKey = "ad:pv:" + adNumVO.getAdId();  // PV键：ad:pv:{adId}
        String uvKey = "ad:uv:" + adNumVO.getAdId();  // UV键：ad:uv:{adId}

        // 增加PV计数并获取新值
        Long newPv = stringRedisTemplate.opsForValue().increment(pvKey, 1);

        // 使用HyperLogLog进行UV统计，每个IP只会被计算一次
        stringRedisTemplate.opsForHyperLogLog().add(uvKey, ipAddress);

        // 获取当前UV数据
        Long uv = stringRedisTemplate.opsForHyperLogLog().size(uvKey);
        if (uv == null || uv == 0) {
            uv = 1L; // 如果获取不到或为0则返回1
        }

        // 设置Redis键在今天结束时过期
        try {
            // 计算当天剩余时间
            Calendar calendar = Calendar.getInstance();
            calendar.set(Calendar.HOUR_OF_DAY, 23);
            calendar.set(Calendar.MINUTE, 59);
            calendar.set(Calendar.SECOND, 59);
            calendar.set(Calendar.MILLISECOND, 999);
            long expireSeconds = (calendar.getTimeInMillis() - System.currentTimeMillis()) / 1000;

            // 设置过期时间
            stringRedisTemplate.expire(pvKey, expireSeconds, TimeUnit.SECONDS);
            stringRedisTemplate.expire(uvKey, expireSeconds, TimeUnit.SECONDS);
        } catch (Exception e) {
            // 如果设置过期时间失败，默认设置为24小时
            stringRedisTemplate.expire(pvKey, 24, TimeUnit.HOURS);
            stringRedisTemplate.expire(uvKey, 24, TimeUnit.HOURS);
        }

        log.info("PV: {}, UV: {}", newPv, uv);
        AdMediumData adMediumData = new AdMediumData();
        adMediumData.setPv(newPv);
        adMediumData.setUv(uv);
        adMediumData.setMediumId(adNumVO.getAdId());
        adMediumData.setDayTime(DateUtil.date());
        iAdMediumDataService.insertAdMediumData(adMediumData);
        AdMedium adMedium = new AdMedium();
        adMedium.setId(adNumVO.getAdId());
        //adMedium.setCompleted();
        adMediumService.updateAdMedium(adMedium);
        // 将当天统计数据保存到广告统计表
        return AjaxResult.success("记录成功");
    }*/

    /*@PostMapping("/recordAddress")
    @ApiOperation("记录点击广告的ip地址接口")
    public AjaxResult recordAddress(HttpServletRequest request, @RequestBody @Valid AdNumVO adNumVO) {
        Long adId = adNumVO.getAdId();
        // 先检查Redis中广告状态缓存，避免已关闭广告的无效处理
        String adStatusKey = "ad:status:" + adId;
        String cachedStatus = stringRedisTemplate.opsForValue().get(adStatusKey);
        if ("3".equals(cachedStatus)) {
            return AjaxResult.success("广告已完成投放");
        }

        // 获取IP地址
        String ipAddress = IpUtils.getIpAddr(request);

        // 使用Redis原子操作增加完成数并检查是否达到投放量
        String completedKey = "ad:completed:" + adId;
        String volumeKey = "ad:volume:" + adId;

        // 使用管道批量处理Redis操作，减少网络往返
        List<Object> results = stringRedisTemplate.executePipelined(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                // 增加完成数
                connection.stringCommands().incr(completedKey.getBytes());
                // 获取完成数
                connection.stringCommands().get(completedKey.getBytes());
                // 获取总投放量
                connection.stringCommands().get(volumeKey.getBytes());
                return null;
            }
        });

        // 从管道结果中获取值
        Long newCompleted = (Long) results.get(0);
        Long currentCompleted = results.get(1) != null ? Long.parseLong((String) results.get(1)) : 0L;
        Long advertisingVolume = results.get(2) != null ? Long.parseLong((String) results.get(2)) : 0L;

        // 如果Redis中没有缓存投放量或完成数，则从数据库获取并缓存
        if (advertisingVolume == 0L || currentCompleted == 0L) {
            AdMedium adMedium = adMediumService.selectAdMediumById(adId);
            if (adMedium != null) {
                // 初始化缓存时，从数据库加载实际值
                if (advertisingVolume == 0L) {
                    advertisingVolume = adMedium.getAdvertisingVolume();
                    stringRedisTemplate.opsForValue().set(volumeKey, String.valueOf(advertisingVolume));
                }

                // 如果Redis中的完成数为0，但数据库中有值，则使用数据库中的值
                if (currentCompleted == 0L && adMedium.getCompleted() != null && adMedium.getCompleted() > 0) {
                    currentCompleted = adMedium.getCompleted();
                    // 重设Redis中的完成数
                    stringRedisTemplate.opsForValue().set(completedKey, String.valueOf(currentCompleted));
                }

                // 初始化时检查广告状态
                if ("3".equals(adMedium.getState())) {
                    stringRedisTemplate.opsForValue().set(adStatusKey, "3");
                    return AjaxResult.success("广告已完成投放");
                }
            }
        }

        // 检查是否达到投放量
        boolean reachedTarget = currentCompleted >= advertisingVolume;
        if (reachedTarget && !"3".equals(cachedStatus)) {
            // 使用分布式锁确保只有一个线程更新广告状态
            String lockKey = "ad:lock:" + adId;
            Boolean lockAcquired = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);

            if (Boolean.TRUE.equals(lockAcquired)) {
                try {
                    // 更新数据库中广告状态
                    AdMedium adMedium = new AdMedium();
                    adMedium.setId(adId);
                    adMedium.setState("3");
                    adMedium.setCompleted(currentCompleted);
                    adMediumService.updateAdMedium(adMedium);

                    // 更新缓存中的状态, 保留状态缓存以便快速判断
                    stringRedisTemplate.opsForValue().set(adStatusKey, "3");

                    // 清理不再需要的Redis键
                    cleanupAdRedisKeys(adId);

                    return AjaxResult.success("广告已完成投放");
                } finally {
                    // 释放锁
                    stringRedisTemplate.delete(lockKey);
                }
            }
        }

        // ===== Redis实现PV和UV统计 =====
        // 定义Redis键及当天过期时间
        LocalDate today = LocalDate.now();
        String dateStr = today.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        String pvKey = "ad:pv:" + adId + ":" + dateStr;
        String uvKey = "ad:uv:" + adId + ":" + dateStr;

        // 批量处理PV和UV统计
        List<Object> statsResults = stringRedisTemplate.executePipelined(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                // 增加PV计数
                connection.stringCommands().incr(pvKey.getBytes());
                // 添加UV统计
                connection.hyperLogLogCommands().pfAdd(uvKey.getBytes(), ipAddress.getBytes());
                // 获取当前PV
                connection.stringCommands().get(pvKey.getBytes());
                // 获取当前UV估计值
                connection.hyperLogLogCommands().pfCount(uvKey.getBytes());
                return null;
            }
        });

        // 获取PV和UV数据
        Long newPv = statsResults.get(2) != null ? Long.parseLong((String) statsResults.get(2)) : 1L;
        Long uv = (Long) statsResults.get(3);
        if (uv == null || uv == 0) {
            uv = 1L;
        }

        // 设置过期时间（当天结束）
        LocalDateTime endOfDay = today.atTime(23, 59, 59);
        Duration duration = Duration.between(LocalDateTime.now(), endOfDay);
        long expireSeconds = duration.getSeconds() > 0 ? duration.getSeconds() : 86400; // 默认24小时

        stringRedisTemplate.executePipelined(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                connection.keyCommands().expire(pvKey.getBytes(), expireSeconds);
                connection.keyCommands().expire(uvKey.getBytes(), expireSeconds);
                return null;
            }
        });

        // 定时批量持久化统计数据（降低数据库写入频率）
        String cacheKey = "ad:stats:cache:" + adId + ":" + dateStr;
        Map<String, String> statsMap = new HashMap<>();
        statsMap.put("pv", String.valueOf(newPv));
        statsMap.put("uv", String.valueOf(uv));
        statsMap.put("timestamp", String.valueOf(System.currentTimeMillis()));
        stringRedisTemplate.opsForHash().putAll(cacheKey, statsMap);

        // 使用基于时间的条件持久化策略
        String lastUpdateKey = "ad:stats:lastupdate:" + adId;
        String lastUpdate = stringRedisTemplate.opsForValue().get(lastUpdateKey);
        long currentTime = System.currentTimeMillis();
        long lastUpdateTime = lastUpdate != null ? Long.parseLong(lastUpdate) : 0;

        // 每5分钟或累计100次点击才写入数据库
        if (currentTime - lastUpdateTime > 300000 || currentCompleted % 1 == 0) {
            stringRedisTemplate.opsForValue().set(lastUpdateKey, String.valueOf(currentTime));

            Long finalCurrentCompleted = currentCompleted;
            Long finalUv = uv;
            CompletableFuture.runAsync(() -> {
                try {
                    // 保存当天PV/UV统计数据
                    AdMediumData adMediumData = new AdMediumData();
                    adMediumData.setPv(newPv);
                    adMediumData.setUv(finalUv);
                    adMediumData.setMediumId(adId);
                    adMediumData.setDayTime(DateUtil.date());
                    iAdMediumDataService.insertAdMediumData(adMediumData);

                    // 更新广告完成数
                    AdMedium adMedium = new AdMedium();
                    adMedium.setId(adId);
                    adMedium.setCompleted(finalCurrentCompleted);
                    adMediumService.updateAdMedium(adMedium);
                } catch (Exception e) {
                    log.error("持久化统计数据异常", e);
                }
            });
        }

        // 异步处理IP记录
        CompletableFuture.runAsync(() -> {
            try {
                // 解析IP地址获取地理位置
                IpRegion currentArea = IpAddressUtil.parseIpLocation(ipAddress);

                // 记录IP信息
                AdIprecord adStatistics = new AdIprecord();
                adStatistics.setArticleId(adId);
                adStatistics.setIpValue(ipAddress);
                adStatistics.setAreaValue(currentArea != null ? currentArea.getCity() : "未知");
                adStatistics.setPv(1L);
                adStatistics.setDayDate(new Date());
                adIprecordService.insertAdIprecord(adStatistics);
            } catch (Exception e) {
                log.error("处理IP记录异常", e);
            }
        });

        return AjaxResult.success("记录成功");
    }*/

   /* @PostMapping("/recordAddress")
    @ApiOperation("记录点击广告的ip地址接口")
    public AjaxResult recordAddress(HttpServletRequest request, @RequestBody @Valid AdNumVO adNumVO) {
        Long adId = adNumVO.getAdId();
        // 先检查Redis中广告状态缓存，避免已关闭广告的无效处理
        String adStatusKey = "ad:status:" + adId;
        String cachedStatus = stringRedisTemplate.opsForValue().get(adStatusKey);
        if ("3".equals(cachedStatus)) {
            return AjaxResult.success("广告已完成投放");
        }

        // 获取IP地址
        String ipAddress = IpUtils.getIpAddr(request);

        // ===== 先处理UV统计，作为完成计数依据 =====
        LocalDate today = LocalDate.now();
        String dateStr = today.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        String uvKey = "ad:uv:" + adId + ":" + dateStr;
        String pvKey = "ad:pv:" + adId + ":" + dateStr;

        // 先检查这个IP今天是否已经统计过UV
        String ipUvCheckKey = "ad:ip:uv:" + adId + ":" + dateStr + ":" + DigestUtils.md5DigestAsHex(ipAddress.getBytes());
        Boolean isNewUv = stringRedisTemplate.opsForValue().setIfAbsent(ipUvCheckKey, "1", 24, TimeUnit.HOURS);

        // 使用管道批量处理Redis操作，减少网络往返
        String completedKey = "ad:completed:" + adId;
        String volumeKey = "ad:volume:" + adId;

        // 先处理PV和UV，并获取当前数值
        Boolean finalIsNewUv = isNewUv;
        stringRedisTemplate.executePipelined(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                // 增加PV计数
                connection.stringCommands().incr(pvKey.getBytes());

                // 如果是新UV，才增加UV计数
                if (Boolean.TRUE.equals(finalIsNewUv)) {
                    // 增加UV计数
                    connection.hyperLogLogCommands().pfAdd(uvKey.getBytes(), ipAddress.getBytes());
                }
                return null;
            }
        });

        // 获取当前的UV计数（HyperLogLog返回的是Long类型）
        Long currentUv = stringRedisTemplate.opsForHyperLogLog().size(uvKey);
        if (currentUv == null || currentUv == 0) {
            currentUv = 1L;
        }

        // 如果是新UV，增加完成数
        Long currentCompleted = 0L;
        if (Boolean.TRUE.equals(isNewUv)) {
            currentCompleted = stringRedisTemplate.opsForValue().increment(completedKey);
        } else {
            // 获取当前完成数
            String completedStr = stringRedisTemplate.opsForValue().get(completedKey);
            currentCompleted = completedStr != null ? Long.parseLong(completedStr) : 0L;
        }

        // 获取投放量
        String volumeStr = stringRedisTemplate.opsForValue().get(volumeKey);
        Long advertisingVolume = volumeStr != null ? Long.parseLong(volumeStr) : 0L;

        // 获取当前PV
        String pvStr = stringRedisTemplate.opsForValue().get(pvKey);
        Long newPv = pvStr != null ? Long.parseLong(pvStr) : 1L;

        // 如果Redis中没有缓存投放量或完成数，则从数据库获取并缓存
        if (advertisingVolume == 0L || currentCompleted == 0L) {
            AdMedium adMedium = adMediumService.selectAdMediumById(adId);
            if (adMedium != null) {
                // 初始化缓存时，从数据库加载实际值
                if (advertisingVolume == 0L) {
                    advertisingVolume = adMedium.getAdvertisingVolume();
                    stringRedisTemplate.opsForValue().set(volumeKey, String.valueOf(advertisingVolume));
                }

                // 如果Redis中的完成数为0，但数据库中有值，则使用数据库中的值
                if (currentCompleted == 0L && adMedium.getCompleted() != null && adMedium.getCompleted() > 0) {
                    currentCompleted = adMedium.getCompleted();
                    // 重设Redis中的完成数
                    stringRedisTemplate.opsForValue().set(completedKey, String.valueOf(currentCompleted));
                }

                // 初始化时检查广告状态
                if ("3".equals(adMedium.getState())) {
                    stringRedisTemplate.opsForValue().set(adStatusKey, "3");
                    return AjaxResult.success("广告已完成投放");
                }
            }
        }

        // 检查是否达到投放量（以UV为准判断）
        boolean reachedTarget = currentCompleted >= advertisingVolume;
        if (reachedTarget && !"3".equals(cachedStatus)) {
            // 使用分布式锁确保只有一个线程更新广告状态
            String lockKey = "ad:lock:" + adId;
            Boolean lockAcquired = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);

            if (Boolean.TRUE.equals(lockAcquired)) {
                try {
                    // 更新数据库中广告状态
                    AdMedium adMedium = new AdMedium();
                    adMedium.setId(adId);
                    adMedium.setState("3");
                    adMedium.setCompleted(currentCompleted); // 保存累计UV作为完成数
                    adMediumService.updateAdMedium(adMedium);

                    // 更新缓存中的状态, 保留状态缓存以便快速判断
                    stringRedisTemplate.opsForValue().set(adStatusKey, "3");

                    // 清理不再需要的Redis键
                    cleanupAdRedisKeys(adId);

                    return AjaxResult.success("广告已完成投放");
                } finally {
                    // 释放锁
                    stringRedisTemplate.delete(lockKey);
                }
            }
        }

        // 设置过期时间（当天结束）
        LocalDateTime endOfDay = today.atTime(23, 59, 59);
        Duration duration = Duration.between(LocalDateTime.now(), endOfDay);
        long expireSeconds = duration.getSeconds() > 0 ? duration.getSeconds() : 86400; // 默认24小时

        stringRedisTemplate.executePipelined(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                connection.keyCommands().expire(pvKey.getBytes(), expireSeconds);
                connection.keyCommands().expire(uvKey.getBytes(), expireSeconds);
                return null;
            }
        });

        // 定时批量持久化统计数据（降低数据库写入频率）
        String cacheKey = "ad:stats:cache:" + adId + ":" + dateStr;
        Map<String, String> statsMap = new HashMap<>();
        statsMap.put("pv", String.valueOf(newPv));
        statsMap.put("uv", String.valueOf(currentUv));
        statsMap.put("timestamp", String.valueOf(System.currentTimeMillis()));
        stringRedisTemplate.opsForHash().putAll(cacheKey, statsMap);

        // 使用基于时间的条件持久化策略
        String lastUpdateKey = "ad:stats:lastupdate:" + adId;
        String lastUpdate = stringRedisTemplate.opsForValue().get(lastUpdateKey);
        long currentTime = System.currentTimeMillis();
        long lastUpdateTime = lastUpdate != null ? Long.parseLong(lastUpdate) : 0;

        // 修改为：每5分钟或累计10个UV才写入数据库
        //if (currentTime - lastUpdateTime > 30000 || (Boolean.TRUE.equals(isNewUv) && currentUv % 5 == 0)) {
            stringRedisTemplate.opsForValue().set(lastUpdateKey, String.valueOf(currentTime));

            Long finalCurrentCompleted = currentCompleted;
            Long finalCurrentUv = currentUv;
            CompletableFuture.runAsync(() -> {
                try {
                    // 保存当天PV/UV统计数据
                    AdMediumData adMediumData = new AdMediumData();
                    adMediumData.setPv(newPv);
                    adMediumData.setUv(finalCurrentUv);
                    adMediumData.setMediumId(adId);
                    adMediumData.setDayTime(DateUtil.date());
                    iAdMediumDataService.insertAdMediumData(adMediumData);

                    // 更新广告完成数
                    AdMedium adMedium = new AdMedium();
                    adMedium.setId(adId);
                    adMedium.setCompleted(finalCurrentCompleted);
                    adMediumService.updateAdMedium(adMedium);
                } catch (Exception e) {
                    log.error("持久化统计数据异常", e);
                }
            });
      //  }

        // 异步处理IP记录
        CompletableFuture.runAsync(() -> {
            try {
                // 解析IP地址获取地理位置
                IpRegion currentArea = IpAddressUtil.parseIpLocation(ipAddress);

                // 记录IP信息
                AdIprecord adStatistics = new AdIprecord();
                adStatistics.setArticleId(adId);
                adStatistics.setIpValue(ipAddress);
                adStatistics.setAreaValue(currentArea != null ? currentArea.getCity() : "未知");
                adStatistics.setPv(1L);
                adStatistics.setDayDate(new Date());
                adIprecordService.insertAdIprecord(adStatistics);
            } catch (Exception e) {
                log.error("处理IP记录异常", e);
            }
        });

        return AjaxResult.success("记录成功");
    }*/

    /*@PostMapping("/recordAddress")
    @ApiOperation("记录点击广告的ip地址接口")
    public AjaxResult recordAddress(HttpServletRequest request, @RequestBody @Valid AdNumVO adNumVO) {
        Long adId = adNumVO.getAdId();
        // 先检查Redis中广告状态缓存，避免已关闭广告的无效处理
        String adStatusKey = "ad:status:" + adId;
        String cachedStatus = stringRedisTemplate.opsForValue().get(adStatusKey);
     *//*   if ("3".equals(cachedStatus)) {
            return AjaxResult.success("广告已完成投放");
        }*//*

        // 获取IP地址
        String ipAddress = IpUtils.getIpAddr(request);

        // 设置缓存键
        String volumeKey = "ad:volume:" + adId;
        String volumeLastUpdateKey = "ad:volume:lastupdate:" + adId;
        String completedKey = "ad:completed:" + adId;

        // 检查投放量缓存是否需要更新
        boolean needUpdateVolume = false;
        String volumeLastUpdateStr = stringRedisTemplate.opsForValue().get(volumeLastUpdateKey);
        long currentTime = System.currentTimeMillis();
        long volumeLastUpdateTime = volumeLastUpdateStr != null ? Long.parseLong(volumeLastUpdateStr) : 0;

        // 如果超过5分钟没有更新投放量，或者缓存中没有投放量，则从数据库更新
        if (currentTime - volumeLastUpdateTime > 300000 ||
                stringRedisTemplate.opsForValue().get(volumeKey) == null) {
            needUpdateVolume = true;
        }

        // 如果需要更新投放量，使用分布式锁避免并发更新
        if (needUpdateVolume) {
            String volUpdateLockKey = "ad:volupdate:lock:" + adId;
            Boolean lockAcquired = stringRedisTemplate.opsForValue().setIfAbsent(volUpdateLockKey, "1", 10, TimeUnit.SECONDS);

            if (Boolean.TRUE.equals(lockAcquired)) {
                try {
                    // 从数据库获取最新投放量
                    AdMedium adMedium = adMediumService.selectAdMediumById(adId);
                    if (adMedium != null) {
                        // 更新投放量缓存
                        if (adMedium.getAdvertisingVolume() != null) {
                            stringRedisTemplate.opsForValue().set(volumeKey, String.valueOf(adMedium.getAdvertisingVolume()));
                            stringRedisTemplate.opsForValue().set(volumeLastUpdateKey, String.valueOf(currentTime));
                            log.info("更新广告(ID:{})投放量缓存: {}", adId, adMedium.getAdvertisingVolume());
                        }

                        // 检查完成数缓存
                        if (stringRedisTemplate.opsForValue().get(completedKey) == null &&
                                adMedium.getCompleted() != null && adMedium.getCompleted() > 0) {
                            stringRedisTemplate.opsForValue().set(completedKey, String.valueOf(adMedium.getCompleted()));
                        }

                        // 检查广告状态
                        if ("3".equals(adMedium.getState())) {
                           // stringRedisTemplate.opsForValue().set(adStatusKey, "3");
                            stringRedisTemplate.expire(adStatusKey, 10, TimeUnit.SECONDS);
                            return AjaxResult.success("广告已完成投放");
                        }
                    }
                } finally {
                    // 释放锁
                    stringRedisTemplate.delete(volUpdateLockKey);
                }
            }
        }

        // ===== 先处理UV统计，作为完成计数依据 =====
        LocalDate today = LocalDate.now();
        String dateStr = today.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        String uvKey = "ad:uv:" + adId + ":" + dateStr;
        String pvKey = "ad:pv:" + adId + ":" + dateStr;

        // 先检查这个IP今天是否已经统计过UV
        String ipUvCheckKey = "ad:ip:uv:" + adId + ":" + dateStr + ":" + DigestUtils.md5DigestAsHex(ipAddress.getBytes());
        Boolean isNewUv = stringRedisTemplate.opsForValue().setIfAbsent(ipUvCheckKey, "1", 24, TimeUnit.HOURS);

        // 先处理PV和UV，并获取当前数值
        Boolean finalIsNewUv = isNewUv;
        stringRedisTemplate.executePipelined(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                // 增加PV计数
                connection.stringCommands().incr(pvKey.getBytes());

                // 如果是新UV，才增加UV计数
                if (Boolean.TRUE.equals(finalIsNewUv)) {
                    // 增加UV计数
                    connection.hyperLogLogCommands().pfAdd(uvKey.getBytes(), ipAddress.getBytes());
                }
                return null;
            }
        });

        // 获取当前的UV计数
        Long currentUv = stringRedisTemplate.opsForHyperLogLog().size(uvKey);
        if (currentUv == null || currentUv == 0) {
            currentUv = 1L;
        }

        // 如果是新UV，增加完成数
        Long currentCompleted = 0L;
        if (Boolean.TRUE.equals(isNewUv)) {
            currentCompleted = stringRedisTemplate.opsForValue().increment(completedKey);
        } else {
            // 获取当前完成数
            String completedStr = stringRedisTemplate.opsForValue().get(completedKey);
            AdMedium adMedium = adMediumService.selectAdMediumById(adId);
            currentCompleted = adMedium != null && adMedium.getCompleted() != null ?
                    adMedium.getCompleted() : 0L;
            // 初始化Redis缓存
            stringRedisTemplate.opsForValue().set(completedKey, String.valueOf(currentCompleted));
           // currentCompleted = completedStr != null ? Long.parseLong(completedStr) : 0L;
        }

        // 获取投放量
        String volumeStr = stringRedisTemplate.opsForValue().get(volumeKey);
        Long advertisingVolume = volumeStr != null ? Long.parseLong(volumeStr) : 0L;

        // 获取当前PV
        String pvStr = stringRedisTemplate.opsForValue().get(pvKey);
        Long newPv = pvStr != null ? Long.parseLong(pvStr) : 1L;

        // 检查是否达到投放量（以UV为准判断）
        boolean reachedTarget = currentCompleted >= advertisingVolume && advertisingVolume > 0;
        if (reachedTarget && !"3".equals(cachedStatus)) {
            // 使用分布式锁确保只有一个线程更新广告状态
            String lockKey = "ad:lock:" + adId;
            Boolean lockAcquired = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);

            if (Boolean.TRUE.equals(lockAcquired)) {
                try {
                    // 更新数据库中广告状态
                    AdMedium adMedium = new AdMedium();
                    adMedium.setId(adId);
                    adMedium.setState("3");
                    adMedium.setCompleted(currentCompleted); // 保存累计UV作为完成数
                    adMediumService.updateAdMedium(adMedium);

                    // 更新缓存中的状态, 保留状态缓存以便快速判断
                    stringRedisTemplate.opsForValue().set(adStatusKey, "3");

                    // 清理不再需要的Redis键
                    cleanupAdRedisKeys(adId);

                    return AjaxResult.success("广告已完成投放");
                } finally {
                    // 释放锁
                    stringRedisTemplate.delete(lockKey);
                }
            }
        }

        // 设置过期时间（当天结束）
        LocalDateTime endOfDay = today.atTime(23, 59, 59);
        Duration duration = Duration.between(LocalDateTime.now(), endOfDay);
        long expireSeconds = duration.getSeconds() > 0 ? duration.getSeconds() : 86400; // 默认24小时

        stringRedisTemplate.executePipelined(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                connection.keyCommands().expire(pvKey.getBytes(), expireSeconds);
                connection.keyCommands().expire(uvKey.getBytes(), expireSeconds);
                return null;
            }
        });

        // 定时批量持久化统计数据（降低数据库写入频率）
        String cacheKey = "ad:stats:cache:" + adId + ":" + dateStr;
        Map<String, String> statsMap = new HashMap<>();
        statsMap.put("pv", String.valueOf(newPv));
        statsMap.put("uv", String.valueOf(currentUv));
        statsMap.put("timestamp", String.valueOf(System.currentTimeMillis()));
        stringRedisTemplate.opsForHash().putAll(cacheKey, statsMap);

        // 使用基于时间的条件持久化策略
        String lastUpdateKey = "ad:stats:lastupdate:" + adId;
        String lastUpdate = stringRedisTemplate.opsForValue().get(lastUpdateKey);
        long lastUpdateTime = lastUpdate != null ? Long.parseLong(lastUpdate) : 0;

        // 修改为：每5分钟或累计10个UV才写入数据库
        if (currentTime - lastUpdateTime > 300000 || (Boolean.TRUE.equals(isNewUv) && currentUv % 1 == 0)) {
            stringRedisTemplate.opsForValue().set(lastUpdateKey, String.valueOf(currentTime));

            Long finalCurrentCompleted = currentCompleted;
            Long finalCurrentUv = currentUv;
            CompletableFuture.runAsync(() -> {
                try {
                    // 保存当天PV/UV统计数据
                    AdMediumData adMediumData = new AdMediumData();
                    adMediumData.setPv(newPv);
                    adMediumData.setUv(finalCurrentUv);
                    adMediumData.setMediumId(adId);
                    adMediumData.setDayTime(DateUtil.date());
                    iAdMediumDataService.insertAdMediumData(adMediumData);

                    // 更新广告完成数
                    AdMedium adMedium = new AdMedium();
                    adMedium.setId(adId);
                    adMedium.setCompleted(finalCurrentCompleted);
                    adMediumService.updateAdMedium(adMedium);
                } catch (Exception e) {
                    log.error("持久化统计数据异常", e);
                }
            });
        }

        // 异步处理IP记录
        CompletableFuture.runAsync(() -> {
            try {
                // 解析IP地址获取地理位置
                IpRegion currentArea = IpAddressUtil.parseIpLocation(ipAddress);

                // 记录IP信息
                AdIprecord adStatistics = new AdIprecord();
                adStatistics.setArticleId(adId);
                adStatistics.setIpValue(ipAddress);
                adStatistics.setAreaValue(currentArea != null ? currentArea.getCity() : "未知");
                adStatistics.setPv(1L);
                adStatistics.setDayDate(new Date());
                adIprecordService.insertAdIprecord(adStatistics);
            } catch (Exception e) {
                log.error("处理IP记录异常", e);
            }
        });

        return AjaxResult.success("记录成功");
    }*/

    @PostMapping("/recordAddress")
    @ApiOperation("记录点击广告的ip地址接口")
    public AjaxResult recordAddress(HttpServletRequest request, @RequestBody @Valid AdNumVO adNumVO) {
        Long adId = adNumVO.getAdId();
        log.info("记录点击广告的ip地址接口 {}", adNumVO);
        String ipAddress = IpUtils.getIpAddr(request);
        // ===== 新增：检查IP是否已访问过该广告 =====
        LocalDate today = LocalDate.now();
        String dateStr = today.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        String ipVisitCheckKey = "ad:ip:visited:" + adId + ":" + dateStr + ":" + DigestUtils.md5DigestAsHex(ipAddress.getBytes());

        // 检查是否已访问（不会设置值，只检查）
        Boolean hasVisited = stringRedisTemplate.hasKey(ipVisitCheckKey);
        log.info("IP {} 今日是否已访问过广告 {}: {}", ipAddress, adId, hasVisited);

        // 先检查Redis中广告状态缓存
        String adStatusKey = "ad:status:" + adId;
        String cachedStatus = stringRedisTemplate.opsForValue().get(adStatusKey);
        /*if ("3".equals(cachedStatus)) {
            return AjaxResult.success("广告已完成投放");
        }*/

        // Redis中没有状态缓存，则从数据库检查广告状态
        AdMedium adMedium = adMediumService.selectAdMediumById(adId);
        if (adMedium != null && "3".equals(adMedium.getState())) {
            // 如果数据库中状态已是"3"，重新设置缓存并返回
            stringRedisTemplate.opsForValue().set(adStatusKey, "3", 10, TimeUnit.SECONDS);
            return AjaxResult.success("广告已完成投放");
        }
        // 获取IP地址


        log.info("获取到的IP地址为：{}", ipAddress);

        // 设置缓存键
        String volumeKey = "ad:volume:" + adId;
        String volumeLastUpdateKey = "ad:volume:lastupdate:" + adId;
        String completedKey = "ad:completed:" + adId;

        long currentTime = System.currentTimeMillis();

        // 如果已经查询了数据库，直接使用查询结果更新缓存
        if (adMedium != null) {
            // 更新投放量缓存
            if (adMedium.getAdvertisingVolume() != null) {
                stringRedisTemplate.opsForValue().set(volumeKey, String.valueOf(adMedium.getAdvertisingVolume()));
                stringRedisTemplate.opsForValue().set(volumeLastUpdateKey, String.valueOf(currentTime));
                log.info("更新广告(ID:{})投放量缓存: {}", adId, adMedium.getAdvertisingVolume());
            }

            // 检查完成数缓存
            /*if (stringRedisTemplate.opsForValue().get(completedKey) == null &&
                    adMedium.getCompleted() != null && adMedium.getCompleted() > 0) {
                stringRedisTemplate.opsForValue().set(completedKey, String.valueOf(adMedium.getCompleted()));
            }*/
            // 在更新缓存的代码块中修改
            if (stringRedisTemplate.opsForValue().get(completedKey) == null) {
                // 如果数据库中有值，使用数据库值；否则从1开始
                Long initialCompleted = adMedium.getCompleted() != null && adMedium.getCompleted() > 0 ?
                        adMedium.getCompleted() : 1L;
                stringRedisTemplate.opsForValue().set(completedKey, String.valueOf(initialCompleted));
            }
        } else {
            // 如果还没有查询数据库，则检查是否需要更新缓存
            boolean needUpdateVolume = false;
            String volumeLastUpdateStr = stringRedisTemplate.opsForValue().get(volumeLastUpdateKey);
            long volumeLastUpdateTime = volumeLastUpdateStr != null ? Long.parseLong(volumeLastUpdateStr) : 0;

            // 如果超过5分钟没有更新投放量，或者缓存中没有投放量，则从数据库更新
            if (currentTime - volumeLastUpdateTime > 300000 ||
                    stringRedisTemplate.opsForValue().get(volumeKey) == null) {
                needUpdateVolume = true;
            }

            // 如果需要更新投放量，使用分布式锁避免并发更新
            if (needUpdateVolume) {
                String volUpdateLockKey = "ad:volupdate:lock:" + adId;
                Boolean lockAcquired = stringRedisTemplate.opsForValue().setIfAbsent(volUpdateLockKey, "1", 10, TimeUnit.SECONDS);

                if (Boolean.TRUE.equals(lockAcquired)) {
                    try {
                        // 从数据库获取最新投放量
                        adMedium = adMediumService.selectAdMediumById(adId);
                        if (adMedium != null) {
                            // 再次检查状态，避免在获取锁期间状态被改变
                            if ("3".equals(adMedium.getState())) {
                                stringRedisTemplate.opsForValue().set(adStatusKey, "3", 10, TimeUnit.SECONDS);
                                return AjaxResult.success("广告已完成投放");
                            }

                            // 更新投放量缓存
                            if (adMedium.getAdvertisingVolume() != null) {
                                stringRedisTemplate.opsForValue().set(volumeKey, String.valueOf(adMedium.getAdvertisingVolume()));
                                stringRedisTemplate.opsForValue().set(volumeLastUpdateKey, String.valueOf(currentTime));
                                log.info("更新广告(ID:{})投放量缓存: {}", adId, adMedium.getAdvertisingVolume());
                            }

                            // 检查完成数缓存
                            if (stringRedisTemplate.opsForValue().get(completedKey) == null &&
                                    adMedium.getCompleted() != null && adMedium.getCompleted() > 0) {
                                stringRedisTemplate.opsForValue().set(completedKey, String.valueOf(adMedium.getCompleted()));
                            }
                        }
                    } finally {
                        // 释放锁
                        stringRedisTemplate.delete(volUpdateLockKey);
                    }
                }
            }
        }

        // ===== 处理UV统计，作为完成计数依据 =====
        //LocalDate today = LocalDate.now();
       // String dateStr = today.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        String uvKey = "ad:uv:" + adId + ":" + dateStr;
        String pvKey = "ad:pv:" + adId + ":" + dateStr;

        if (!hasVisited) {
            // 设置访问标记，24小时过期
            stringRedisTemplate.opsForValue().set(ipVisitCheckKey, "1", 24, TimeUnit.HOURS);
        }

        // 先检查这个IP今天是否已经统计过UV
        String ipUvCheckKey = "ad:ip:uv:" + adId + ":" + dateStr + ":" + DigestUtils.md5DigestAsHex(ipAddress.getBytes());
        Boolean isNewUv = stringRedisTemplate.opsForValue().setIfAbsent(ipUvCheckKey, "1", 24, TimeUnit.HOURS);

        // 处理PV和UV，并获取当前数值
        Boolean finalIsNewUv = isNewUv;
        stringRedisTemplate.executePipelined(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                // 增加PV计数
                connection.stringCommands().incr(pvKey.getBytes());

                // 如果是新UV，才增加UV计数
                if (Boolean.TRUE.equals(finalIsNewUv)) {
                    // 增加UV计数
                    connection.hyperLogLogCommands().pfAdd(uvKey.getBytes(), ipAddress.getBytes());
                }
                return null;
            }
        });

        // 获取当前的UV计数
        Long currentUv = stringRedisTemplate.opsForHyperLogLog().size(uvKey);
        if (currentUv == null || currentUv == 0) {
            currentUv = 1L;
        }

        // 如果是新UV，增加完成数
        Long currentCompleted = 0L;
        if (Boolean.TRUE.equals(isNewUv)) {
            currentCompleted = stringRedisTemplate.opsForValue().increment(completedKey);
        } else {
            // 获取当前完成数
            String completedStr = stringRedisTemplate.opsForValue().get(completedKey);
            if (completedStr != null) {
                currentCompleted = Long.parseLong(completedStr);
            } else {
                // Redis没有数据，从数据库获取
                if (adMedium == null) {
                    adMedium = adMediumService.selectAdMediumById(adId);
                }
                currentCompleted = adMedium != null && adMedium.getCompleted() != null ?
                        adMedium.getCompleted() : 1L;
                // 初始化Redis缓存
                stringRedisTemplate.opsForValue().set(completedKey, String.valueOf(currentCompleted));
            }
        }

        // 获取投放量
        String volumeStr = stringRedisTemplate.opsForValue().get(volumeKey);
        Long advertisingVolume = volumeStr != null ? Long.parseLong(volumeStr) : 0L;

        // 获取当前PV
        String pvStr = stringRedisTemplate.opsForValue().get(pvKey);
        Long newPv = pvStr != null ? Long.parseLong(pvStr) : 1L;

        // ===== 先同步处理IP记录和统计数据 =====
        // 1. 处理IP记录
        IpRegion currentArea = null;
        try {
            // 解析IP地址获取地理位置
         //   currentArea = IpAddressUtil.parseIpLocation(ipAddress);

            // 记录IP信息
            AdIprecord adStatistics = new AdIprecord();
            adStatistics.setArticleId(adId);
            adStatistics.setIpValue(ipAddress);
          //  adStatistics.setAreaValue(currentArea != null ? currentArea.getCity() : "未知");
            adStatistics.setPv(1L);
            adStatistics.setDayDate(new Date());
            adIprecordService.insertAdIprecord(adStatistics);
        } catch (Exception e) {
            log.error("处理IP记录异常", e);
        }

        // 2. 处理统计数据
        try {
            // 保存当天PV/UV统计数据
            AdMediumData adMediumData = new AdMediumData();
            adMediumData.setPv(newPv);
            adMediumData.setUv(currentUv);
            adMediumData.setMediumId(adId);
            adMediumData.setDayTime(DateUtil.date());
            iAdMediumDataService.insertAdMediumData(adMediumData);

            // 更新广告完成数
            AdMedium adMediumUpdate = new AdMedium();
            adMediumUpdate.setId(adId);
            adMediumUpdate.setCompleted(currentCompleted);
            adMediumService.updateAdMedium(adMediumUpdate);
        } catch (Exception e) {
            log.error("持久化统计数据异常", e);
        }

        // ===== 现在再检查是否达到投放量 =====
        boolean reachedTarget = currentCompleted >= advertisingVolume && advertisingVolume > 0;
        if (reachedTarget) {
            // 再次检查数据库状态，避免状态已被其他线程更新
            if (adMedium == null || !"3".equals(adMedium.getState())) {
                // 使用分布式锁确保只有一个线程更新广告状态
                String lockKey = "ad:lock:" + adId;
                Boolean lockAcquired = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);

                if (Boolean.TRUE.equals(lockAcquired)) {
                    try {
                        // 更新数据库中广告状态
                        AdMedium adMediumStateUpdate = new AdMedium();
                        adMediumStateUpdate.setId(adId);
                        adMediumStateUpdate.setState("3");
                        adMediumService.updateAdMedium(adMediumStateUpdate);

                        // 更新缓存中的状态，设置10秒过期时间
                        stringRedisTemplate.opsForValue().set(adStatusKey, "3", 10, TimeUnit.SECONDS);

                        // 清理不再需要的Redis键
                        cleanupAdRedisKeys(adId);

                        return AjaxResult.success("广告已完成投放");
                    } finally {
                        // 释放锁
                        stringRedisTemplate.delete(lockKey);
                    }
                }
            } else {
                // 数据库状态已是完成，更新缓存并返回
                stringRedisTemplate.opsForValue().set(adStatusKey, "3", 10, TimeUnit.SECONDS);
                return AjaxResult.success("广告已完成投放");
            }
        }

        // 设置过期时间（当天结束）
        LocalDateTime endOfDay = today.atTime(23, 59, 59);
        Duration duration = Duration.between(LocalDateTime.now(), endOfDay);
        long expireSeconds = duration.getSeconds() > 0 ? duration.getSeconds() : 86400; // 默认24小时

        stringRedisTemplate.executePipelined(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                connection.keyCommands().expire(pvKey.getBytes(), expireSeconds);
                connection.keyCommands().expire(uvKey.getBytes(), expireSeconds);
                return null;
            }
        });

        // 定时批量持久化统计数据（降低数据库写入频率）
        String cacheKey = "ad:stats:cache:" + adId + ":" + dateStr;
        Map<String, String> statsMap = new HashMap<>();
        statsMap.put("pv", String.valueOf(newPv));
        statsMap.put("uv", String.valueOf(currentUv));
        statsMap.put("timestamp", String.valueOf(currentTime));
        stringRedisTemplate.opsForHash().putAll(cacheKey, statsMap);

        // 更新最后处理时间
        String lastUpdateKey = "ad:stats:lastupdate:" + adId;
        stringRedisTemplate.opsForValue().set(lastUpdateKey, String.valueOf(currentTime));

        Map<String, Boolean> cachedStats = new HashMap<String,Boolean>();

        cachedStats.put("hasVisited", hasVisited);

        return AjaxResult.success(cachedStats);
    }

    /**
     * 清理广告相关的Redis键
     *
     * @param adId 广告ID
     */
    private void cleanupAdRedisKeys(Long adId) {
        try {
            // 保留广告状态键，用于快速判断广告是否完成
            // 其他与广告相关的键可以清理

            // 获取要清理的键模式
            Set<String> keysToDelete = new HashSet<>();

            // 完成数和投放量键
            keysToDelete.add("ad:completed:" + adId);
            keysToDelete.add("ad:volume:" + adId);

            // 最后更新时间键
            keysToDelete.add("ad:stats:lastupdate:" + adId);

            // 使用SCAN命令查找包含adId的今日和历史统计数据键
            // 注意：此处使用模式匹配，可能会扫描较多键，但更安全
            String pattern = "ad:stats:cache:" + adId + ":*";
            Set<String> scanKeys = scanKeys(pattern);
            keysToDelete.addAll(scanKeys);

            // 查找当天及历史的PV/UV键
            pattern = "ad:pv:" + adId + ":*";
            scanKeys = scanKeys(pattern);
            keysToDelete.addAll(scanKeys);

            pattern = "ad:uv:" + adId + ":*";
            scanKeys = scanKeys(pattern);
            keysToDelete.addAll(scanKeys);

            // 批量删除键
            if (!keysToDelete.isEmpty()) {
                stringRedisTemplate.delete(keysToDelete);
                log.info("已清理完成投放的广告(ID:{})相关Redis键，共{}个", adId, keysToDelete.size());
            }
        } catch (Exception e) {
            log.error("清理广告Redis键异常，广告ID: " + adId, e);
        }
    }

    /**
     * 使用SCAN命令查找匹配模式的键
     *
     * @param pattern 键模式
     * @return 匹配的键集合
     */
    private Set<String> scanKeys(String pattern) {
        Set<String> keys = new HashSet<>();
        stringRedisTemplate.execute(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                ScanOptions options = ScanOptions.scanOptions().match(pattern).count(100).build();
                Cursor<byte[]> cursor = connection.keyCommands().scan(options);
                while (cursor.hasNext()) {
                    keys.add(new String(cursor.next()));
                }
                // 确保关闭Cursor
                try {
                    cursor.close();
                } catch (Exception e) {
                    log.error("关闭Redis scan cursor异常", e);
                }
                return null;
            }
        });
        return keys;
    }





}
